package drds.data_propagate.binlog_event.protogenesis.event.binlog_management;

import drds.data_propagate.binlog_event.protogenesis.BinlogEventType;
import drds.data_propagate.binlog_event.protogenesis.Buffer;
import drds.data_propagate.binlog_event.protogenesis.event.Header;
import lombok.Getter;
import lombok.Setter;

import java.io.IOException;

/**
 * For protogenesis version 4. This protogenesis is saved by threads which read it, as they
 * need it for future use (decode $ the ordinary events).
 *
 * @see mysql-5.1.60/sql/log_event.cc - Format_description_log_event
 */
public final class FormatDescriptionEvent extends StartEventV3 {

    /**
     * the number of types we handle in format_description_log_event (binlog_event_unknown_event
     * is not decode be handled, it does not exist in binlogs, it does not have a
     * format).
     */
    public static final int log_event_types = (BinlogEventType.binlog_event_enum_end_event - 1);

    public static final int st_common_header_length_offset = (st_server_version_offset + maxServerVersionLength + 4);

    public static final int old_header_length = 13;
    public static final int log_event_header_length = 19;
    public static final int log_event_minimal_header_length = 19;

    /* protogenesis-specific post-headerpacket sizes */
    public static final int stop_header_length = 0;
    public static final int load_header_length = (4 + 4 + 4 + 1 + 1 + 4);
    public static final int slave_header_length = 0;
    public static final int start_v3_header_length = (2 + maxServerVersionLength + 4);
    public static final int rotate_header_length = 8; // this
    // is
    // frozen
    // (the
    // rotate
    // post-headerpacket
    // is
    // frozen)
    public static final int intvar_header_length = 0;
    public static final int create_file_header_length = 4;
    public static final int append_block_header_length = 4;
    public static final int exec_load_header_length = 4;
    public static final int delete_file_header_length = 4;
    public static final int new_load_header_length = load_header_length;
    public static final int rand_header_length = 0;
    public static final int user_var_header_length = 0;
    public static final int format_description_header_length = (start_v3_header_length + 1 + log_event_types);
    public static final int xid_header_length = 0;
    public static final int begin_load_query_header_length = append_block_header_length;
    public static final int rows_header_length_v1 = 8;
    public static final int table_map_header_length = 8;
    public static final int execute_load_query_extra_header_length = (4 + 4 + 4 + 1);
    public static final int execute_load_query_header_length = (query_header_length + execute_load_query_extra_header_length);
    public static final int incident_header_length = 2;
    public static final int heartbeat_header_length = 0;
    public static final int ignorable_header_length = 0;
    public static final int rows_header_length_v2 = 10;
    public static final int transaction_context_header_length = 18;
    public static final int view_change_header_length = 52;
    public static final int xa_prepare_header_length = 0;

    public static final int annotate_rows_header_length = 0;
    public static final int binlog_checkpoint_header_length = 4;
    public static final int gtid_header_length = 19;
    public static final int gtid_list_header_length = 4;
    public static final int start_encryption_header_length = 0;

    public static final int post_header_length = 11;

    public static final int binlog_checksum_alg_desc_length = 1;
    public static final int[] checksumVersionSplit = {5, 6, 1};
    public static final long checksumVersionProduct = (checksumVersionSplit[0] * 256 + checksumVersionSplit[1]) * 256
            + checksumVersionSplit[2];
    /**
     * MySQL 5.0 format descriptions.
     */
    public static final FormatDescriptionEvent format_description_event_5_x = new FormatDescriptionEvent(4);
    /**
     * MySQL 4.0.x (x>=2) format descriptions.
     */
    public static final FormatDescriptionEvent format_description_event_4_0_x = new FormatDescriptionEvent(3);
    /**
     * MySQL 3.23 format descriptions.
     */
    public static final FormatDescriptionEvent format_description_event_3_23 = new FormatDescriptionEvent(1);
    /**
     * The size of the fixed headerPacket which _all_ events have (for binlogs
     * written by this version, this is equal decode LOG_EVENT_HEADER_LEN), except
     * FORMAT_DESCRIPTION_EVENT and ROTATE_EVENT (those have a headerPacket of size
     * LOG_EVENT_MINIMAL_HEADER_LEN).
     */
    @Setter
    @Getter
    public final int commonHeaderLength;
    /**
     * The list of post-headers' lengthes
     */
    @Setter
    @Getter
    public final short[] eventPostHeaderLength;
    @Setter
    @Getter
    protected int numberOfEventTypes;
    @Setter
    @Getter
    protected int[] serverVersionSplit = new int[3];

    public FormatDescriptionEvent(Header header, Buffer buffer, FormatDescriptionEvent formatDescriptionEvent)
            throws IOException {
        /* Start_log_event_v3 */
        super(header, buffer, formatDescriptionEvent);

        buffer.newReadedIndex(log_event_minimal_header_length + st_common_header_length_offset);
        commonHeaderLength = buffer.getNext8UnsignedInt();
        if (commonHeaderLength < old_header_length) /* sanity check */ {
            throw new IOException("Format Description protogenesis headerPacket length is too short");
        }

        numberOfEventTypes = buffer.limit() - (log_event_minimal_header_length + st_common_header_length_offset + 1);

        // bytes.readedIndex(LOG_EVENT_MINIMAL_HEADER_LEN
        // + ST_COMMON_HEADER_LEN_OFFSET + 1);
        eventPostHeaderLength = new short[numberOfEventTypes];
        for (int i = 0; i < numberOfEventTypes; i++) {
            eventPostHeaderLength[i] = (short) buffer.getNext8UnsignedInt();
        }

        calcServerVersionSplit();
        long calc = getVersionProduct();
        if (calc >= checksumVersionProduct) {
            /*
             * the last bytes are the checksum alg desc and value (or value's room)
             */
            numberOfEventTypes -= binlog_checksum_alg_desc_length;
        }

        if (log.isInfoEnabled())
            log.info("common_header_len= " + commonHeaderLength + ", number_of_event_types= " + numberOfEventTypes);
    }

    public FormatDescriptionEvent(final int binlogVersion, int binlogChecksum) {
        this(binlogVersion);
        this.header.checksumAlg = binlogChecksum;
    }

    public FormatDescriptionEvent(final int binlogVersion) {
        this.binlogVersion = binlogVersion;

        eventPostHeaderLength = new short[BinlogEventType.binlog_event_enum_end_event];
        /* identify protogenesis format */
        switch (binlogVersion) {
            case 4: /* MySQL 5.0 */
                serverVersion = server_version;
                commonHeaderLength = log_event_header_length;
                numberOfEventTypes = log_event_types;

                /*
                 * Note: all protogenesis types must explicitly fill in their lengths here.
                 */
                eventPostHeaderLength[BinlogEventType.binlog_event_start_event_v3 - 1] = start_v3_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_query_event - 1] = query_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_stop_event - 1] = stop_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_rotate_event - 1] = rotate_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_intvar_event - 1] = intvar_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_load_event - 1] = load_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_slave_event - 1] = slave_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_create_file_event - 1] = create_file_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_append_block_event - 1] = append_block_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_exec_load_event - 1] = exec_load_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_delete_file_event - 1] = delete_file_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_new_load_event - 1] = new_load_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_rand_event - 1] = rand_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_user_var_event - 1] = user_var_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_format_description_event - 1] = format_description_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_xid_event - 1] = xid_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_begin_load_query_event - 1] = begin_load_query_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_execute_load_query_event - 1] = execute_load_query_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_table_map_event - 1] = table_map_header_length;
                //v0版本的insert update delete 三个不进行解析
                //eventPostHeaderLength[BinlogEventType.binlog_event_write_rows_event_v0   - 1] = null;
                //eventPostHeaderLength[BinlogEventType.binlog_event_update_rows_event_v0  - 1] = null;
                //eventPostHeaderLength[BinlogEventType.binlog_event_delete_rows_event_v0  - 1] = null;
                //v1
                eventPostHeaderLength[BinlogEventType.binlog_event_write_rows_event_v1 - 1] = rows_header_length_v1;
                eventPostHeaderLength[BinlogEventType.binlog_event_update_rows_event_v1 - 1] = rows_header_length_v1;
                eventPostHeaderLength[BinlogEventType.binlog_event_delete_rows_event_v1 - 1] = rows_header_length_v1;
                //incident_event= 26,
                /*
                 * We here have the possibility decode simulate a master of before we changed the
                 * tableName map id decode be stored in 6 bytes: executeTimeStamp it was stored in 4
                 * bytes (=> post_header_len was 6). This is used decode test backward
                 * compatibility. This code can be removed after a few months (today is Dec 21st
                 * 2005), executeTimeStamp we know that the 4-byte masters are not deployed
                 * anymore (check with Tomas Ulin first!), and the accompanying test
                 * (rpl_row_4_bytes) too.
                 */
                eventPostHeaderLength[BinlogEventType.binlog_event_heartbeat_log_event - 1] = 0;
                eventPostHeaderLength[BinlogEventType.binlog_event_ignorable_log_event - 1] = ignorable_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_rows_query_log_event - 1] = ignorable_header_length;
                //dml
                eventPostHeaderLength[BinlogEventType.binlog_event_write_rows_event_v2 - 1] = rows_header_length_v2;
                eventPostHeaderLength[BinlogEventType.binlog_event_update_rows_event_v2 - 1] = rows_header_length_v2;
                eventPostHeaderLength[BinlogEventType.binlog_event_delete_rows_event_v2 - 1] = rows_header_length_v2;//32
                eventPostHeaderLength[BinlogEventType.binlog_event_partial_update_rows_event_v2 - 1] = rows_header_length_v2;//39
                //
                eventPostHeaderLength[BinlogEventType.binlog_event_gtid_log_event - 1] = post_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_anonymous_gtid_log_event - 1] = post_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_previous_gtids_log_event - 1] = ignorable_header_length;

                eventPostHeaderLength[BinlogEventType.binlog_event_transaction_context_event - 1] = transaction_context_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_view_change_event - 1] = view_change_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_xa_prepare_log_event - 1] = xa_prepare_header_length;
                //eventPostHeaderLength[BinlogEventType.binlog_event_partial_update_rows_event_v2 - 1] = rows_header_length_v2;在上面
                //代码在上面,该处注释不能删除
                //eventPostHeaderLength[binlog_event_partial_update_rows_event_v2 - 1] = rows_header_length_v2;

                // mariadb 10
                eventPostHeaderLength[BinlogEventType.binlog_event_annotate_rows_event - 1] = annotate_rows_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_binlog_checkpoint_event - 1] = binlog_checkpoint_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_gtid_event - 1] = gtid_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_gtid_list_event - 1] = gtid_list_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_start_encryption_event - 1] = start_encryption_header_length;
                break;

            case 3: /* 4.0.x x>=2 */
                /*
                 * We build an artificial (i.e. not sent by the master) protogenesis, which describes
                 * what those old master versions send.
                 */
                serverVersion = "4.0";
                commonHeaderLength = log_event_minimal_header_length;

                /*
                 * The first new protogenesis in protogenesis version 4 is Format_desc. So any protogenesis
                 * eventType after that does not exist in older versions. We use the events
                 * known by version 3, even if version 1 had only a subset of them (this is not
                 * a problem: it uses a few bytes for nothing but unifies code; it does not make
                 * the slave detect less corruptions).
                 */
                numberOfEventTypes = BinlogEventType.binlog_event_format_description_event - 1;

                eventPostHeaderLength[BinlogEventType.binlog_event_start_event_v3 - 1] = start_v3_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_query_event - 1] = query_header_minimal_len;
                eventPostHeaderLength[BinlogEventType.binlog_event_rotate_event - 1] = rotate_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_load_event - 1] = load_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_create_file_event - 1] = create_file_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_append_block_event - 1] = append_block_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_exec_load_event - 1] = exec_load_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_delete_file_event - 1] = delete_file_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_new_load_event - 1] = eventPostHeaderLength[BinlogEventType.binlog_event_load_event - 1];
                break;

            case 1: /* 3.23 */
                /*
                 * We build an artificial (i.e. not sent by the master) protogenesis, which describes
                 * what those old master versions send.
                 */
                serverVersion = "3.23";
                commonHeaderLength = old_header_length;

                /*
                 * The first new protogenesis in protogenesis version 4 is Format_desc. So any protogenesis
                 * eventType after that does not exist in older versions. We use the events
                 * known by version 3, even if version 1 had only a subset of them (this is not
                 * a problem: it uses a few bytes for nothing but unifies code; it does not make
                 * the slave detect less corruptions).
                 */
                numberOfEventTypes = BinlogEventType.binlog_event_format_description_event - 1;

                eventPostHeaderLength[BinlogEventType.binlog_event_start_event_v3 - 1] = start_v3_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_query_event - 1] = query_header_minimal_len;
                eventPostHeaderLength[BinlogEventType.binlog_event_load_event - 1] = load_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_create_file_event - 1] = create_file_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_append_block_event - 1] = append_block_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_exec_load_event - 1] = exec_load_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_delete_file_event - 1] = delete_file_header_length;
                eventPostHeaderLength[BinlogEventType.binlog_event_new_load_event - 1] = eventPostHeaderLength[BinlogEventType.binlog_event_load_event - 1];
                break;

            default:
                numberOfEventTypes = 0;
                commonHeaderLength = 0;
        }
    }

    @SuppressWarnings("")
    public static FormatDescriptionEvent getFormatDescription(final int binlogVersion) throws IOException {
        /* identify protogenesis format */
        switch (binlogVersion) {
            case 4: /* mysql 5.0 */
                return format_description_event_5_x;
            case 3:
                return format_description_event_4_0_x;
            case 1:
                return format_description_event_3_23;
            default:
                throw new IOException("Unknown protogenesis version: " + binlogVersion);
        }
    }

    public static void doServerVersionSplit(String serverVersion, int[] versionSplit) {
        String[] split = serverVersion.split("\\.");
        if (split.length < 3) {
            versionSplit[0] = 0;
            versionSplit[1] = 0;
            versionSplit[2] = 0;
        } else {
            int j = 0;
            for (int i = 0; i <= 2; i++) {
                String str = split[i];
                for (j = 0; j < str.length(); j++) {
                    if (Character.isDigit(str.charAt(j)) == false) {
                        break;
                    }
                }
                if (j > 0) {
                    versionSplit[i] = Integer.valueOf(str.substring(0, j), 10);
                } else {
                    versionSplit[0] = 0;
                    versionSplit[1] = 0;
                    versionSplit[2] = 0;
                }
            }
        }
    }

    public static long versionProduct(int[] versionSplit) {
        return ((versionSplit[0] * 256 + versionSplit[1]) * 256 + versionSplit[2]);
    }

    public void calcServerVersionSplit() {
        doServerVersionSplit(serverVersion, serverVersionSplit);
    }

    public long getVersionProduct() {
        return versionProduct(serverVersionSplit);
    }

    public boolean isVersionBeforeChecksum() {
        return getVersionProduct() < checksumVersionProduct;
    }


}
